#include "proxy/encry/encryclientconn.h"

namespace proxy{

  EncryClientConn::EncryClientConn(boost::asio::io_service &io_service)
    :io_service_(io_service),
    socket_(io_service),
    is_write_encry_(false),
    is_read_encry_(false),
    remain_header_size_(0),
    key_('G'){
  }

  EncryClientConn::~EncryClientConn(){
    DLOG(WARNING) << "Delete";
  }

  bool EncryClientConn::Start(const std::string addr, uint16 port){

    boost::asio::ip::tcp::endpoint server_addr(
      boost::asio::ip::address().from_string(addr), port);

    socket_.async_connect(server_addr,
      boost::bind(&EncryClientConn::HandleServerConnect, shared_from_this(),
      boost::asio::placeholders::error));

    return true;
  }

  void EncryClientConn::Stop(){
    if (socket_.is_open()){
      socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
    }
  }

  bool EncryClientConn::AsyncWriteData(char *data, int size){
    if (!is_write_encry_){
      std::vector<boost::asio::const_buffer> buffers;
      buffers.push_back(boost::asio::buffer(MakeClientKey()));
      EncryData(data, size, key_);
      buffers.push_back(boost::asio::buffer(data, size));
      boost::asio::async_write(socket_,
        buffers,
        boost::bind(&EncryClientConn::HandleEncryWrite, shared_from_this(),
        boost::asio::placeholders::error));
      is_write_encry_ = true;
    }
    else{
      EncryData(data, size, key_);
      boost::asio::async_write(socket_,
        boost::asio::buffer(data, size),
        boost::bind(&EncryClientConn::HandleEncryWrite, shared_from_this(),
        boost::asio::placeholders::error));
    }
    return true;
  }

  std::string &EncryClientConn::MakeClientKey(){
    int r = rand() % ENCRY_REQUEST_SET_SIZE;
    for (int i = 0; i < ENCRY_REQUEST_SET[r].encry_header_size; i++){
      if (ENCRY_REQUEST_SET[r].encry_header[i] == HTTP_KEY){
        AddRandomChar(make_key_str_, rand() % RANDOM_SIZE + 1);
        make_key_str_.push_back(HTTP_KEY);
        key_ = AddRandomChar(make_key_str_, rand() % RANDOM_SIZE + 1);
      }
      else{
        make_key_str_.push_back(ENCRY_REQUEST_SET[r].encry_header[i]);
      }
    }
    return make_key_str_;
  }

  void EncryClientConn::HandleServerConnect(const boost::system::error_code& err){
    if (err){
      DLOG(ERROR) << "Connect server error";
      SingalConnServer(shared_from_this(), err);
      return;
    }
    SingalConnServer(shared_from_this(), err);
    StartReadEncryData();
  }

  void EncryClientConn::StartReadEncryData(){
    socket_.async_read_some(boost::asio::buffer(read_buffer_, MAX_BUFFER_SIZE),
      boost::bind(&EncryClientConn::HandleReadEncryData, shared_from_this(),
      boost::asio::placeholders::error, 
      boost::asio::placeholders::bytes_transferred));
  }

  void EncryClientConn::HandleReadEncryData(const boost::system::error_code& err, int size){
    if (err){
      DLOG(ERROR) << err.message();
      SignalConnError(shared_from_this(), err, ERROR_TYPE_READ_ERROR);
      return;
    }
    //DLOG(INFO).write(read_buffer_, size);
    if (!is_read_encry_){
      int total_size = remain_header_size_ + size;
      int res = AnysisReadEncry(read_buffer_, total_size);
      if (res == -1){
        remain_header_size_ = total_size;
        socket_.async_read_some(boost::asio::buffer(
          read_buffer_ + total_size, MAX_BUFFER_SIZE - total_size),
          boost::bind(&EncryClientConn::HandleReadEncryData, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
      }
      else{
        is_read_encry_ = true;
        DecodeData(read_buffer_ + res, total_size - res, key_);
        SingalConnRead(shared_from_this(), read_buffer_ + res, total_size - res);
      }
    }
    else{
      DecodeData(read_buffer_, size, key_);
      SingalConnRead(shared_from_this(), read_buffer_, size);
      //StartReadEncryData();
    }
  }

  int EncryClientConn::AnysisReadEncry(const char *buffer, int size){
    for (int i = 0; i < size - ENCRY_END_SIZE; i++){
      if (strncmp(buffer + i, ENCRY_END_CHAR_SET, ENCRY_END_SIZE) == 0){
        return i + ENCRY_END_SIZE;
      }
    }
    return -1;
  }

  void EncryClientConn::HandleEncryWrite(const boost::system::error_code& err){
    if (err){
      DLOG(ERROR) << "Write Encry Data error";
      SignalConnError(shared_from_this(), err, ERROR_TYPE_WRITE_ERROR);
      return;
    }
    SignalConnWrite(shared_from_this());
  }
}